import base64
import pickle

from django.shortcuts import render

# Create your views here.
from django_redis import get_redis_connection
from rest_framework import status
from rest_framework.response import Response
from rest_framework.views import APIView

# POST /cart/
from cart import constants
from cart.serializers import CartSerializer, CartSKUSerializer, CartDelSerializer, CartSelectSerializer
from goods.models import SKU


class CartView(APIView):
    # 由于未登录（无效的jwt token值）用户，在向服务器发出请求还没调用接口前，DRF框架要进行认证，所以为了上面客户能够正常访问我们，此处要对DRF框架 APIView中的perform_authentication进行重写
    def perform_authentication(self, request):
        """重写APIView中的perform_authentication，跳过DRF框架的认证过程"""
        pass

    def delete(self, request):
        """
        1、获取用户的购物车记录
            1.1 如果用户已登录，则从redis中获取登录用户的购物车信息  hash中的商品id和count数量 set中的商品状态 seleted
            1.2 如果用户未登录，则从cookie中获取用户的购物车信息
        2、删除指定购物车中记录
        3、返回应答 删除成功

        """
        # 1、获取参数并进行校验（参数的完整性、sku_id对应商品是否存在，存在的话，count值是否大于库存量）
        serializer = CartDelSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)

        # 获取校验后的商品sku_id
        sku_id = serializer.validated_data['sku_id']

        # 获取user
        try:
            user = request.user
        except Exception:
            user = None

        # 2、删除用户的对应的购物车记录
        if user and user.is_authenticated:
            # 如果用户已登录，删除对应该redis中对应的购物车记录
            # 建立连接
            redis_conn = get_redis_connection('cart')
            # 从redis中的hash中删除sku_id对应该的属性和值
            # 获取键
            cart_key = 'cart_%s' % user.id
            # 删除
            redis_conn.hdel(cart_key, sku_id)

            # 从redis的set中移除sku_id勾选状态
            # 获取键
            cart_selected_key = 'cart_selected_%s' % user.id
            # 删除值
            redis_conn.srem(cart_selected_key, sku_id)

            # 返回响应
            return Response(status=status.HTTP_204_NO_CONTENT)

        else:
            # 2.2 用户未登录 从cookie中删除对应的购物车记录
            response = Response(status=status.HTTP_204_NO_CONTENT)
            # 获取cookie中的数据
            cookie_cart = request.COOKIES.get('cart')  # NONE

            if cookie_cart is None:
                # 说明购物车无数据
                return response
            cart_dict = pickle.loads(base64.b64decode(cookie_cart.encode()))  # {}

            if not cart_dict:
                # 字典为空 购物车无数据
                return response

            # 删除购物车中对应的购物车记录
            if sku_id in cart_dict:
                del cart_dict[sku_id]
                cart_data = base64.b64encode(pickle.dumps(cart_dict)).decode()
                response.set_cookie('cart', cart_dict, max_age=constants.CART_COOKIE_EXPIRES)

            # 3、返回应答 删除成功
            return response

    def put(self, request):
        """
        获取购物车记录
        1、获取用户的购物车记录
            1.1 如果用户已登录，则从redis中获取登录用户的购物车信息  hash中的商品id和count数量 set中的商品状态 seleted
            1.2 如果用户未登录，则从cookie中获取用户的购物车信息
        2、修改购物车中的信息
        3、返回应答 修改成功
        """
        # 1、获取参数并进行校验（参数的完整性、sku_id对应商品是否存在，存在的话，count值是否大于库存量）
        serializer = CartSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        # 获取校验后数据
        sku_id = serializer.validated_data['sku_id']  # 修改商品的id
        count = serializer.validated_data['count']  # 修改数量
        selected = serializer.validated_data['selected']  # 修改勾选状态

        # 获取user
        try:
            user = request.user
        except Exception:
            user = None

        # 2、修改用户购物车中的数据
        # 2.1已登录用户
        if user and user.is_authenticated:
            # 修改已登录用户的redis中的数据
            # 建立连接
            redis_conn = get_redis_connection('cart')

            # 修改已登录用户的购物信息
            # 从redis hash获取用户的键
            cart_key = 'cart_%s' % user.id
            redis_conn.hset(cart_key, sku_id, count)

            # 修改登录用户redis中set的勾选状态
            # 获取set键
            cart_selected_key = 'cart_selected_%s' % user.id
            # 做出判断，是否存在
            # selected字段为布尔值，用户点击传入True或者Fault表示勾选或者不勾选
            if selected:
                # 为勾选状态  修改此时的勾选状态 即记录存储sku_id值
                redis_conn.sadd(cart_selected_key, sku_id)
            else:
                # 说明为未勾选状态
                redis_conn.srem(cart_selected_key, sku_id)
            return Response(serializer.validated_data)

        else:
            # 表示用户是未登录状态 用户的购物记录保存在cookie中 修改用户cookie中的数据
            response = Response(serializer.validated_data)

            # 获取cookie中的购物车数据
            cookie_cart = request.COOKIES.get('cart')

            if cookie_cart is None:
                # 购物车无数据
                return response

            cart_dict = pickle.loads(base64.b64decode(cookie_cart.encode()))  # {}

            if not cart_dict:
                # 说明字典为空
                return response

            # 保存修改的商品数量和勾选状态
            cart_dict[sku_id] = {
                'count': count,
                'selected': selected
            }

            # 3、返回应答，修改成功
            # 设置cookie购物车数据
            cart_data = base64.b64encode(pickle.dumps(cart_dict)).decode()
            response.set_cookie('cart', cart_data, max_age=constants.CART_COOKIE_EXPIRES)
            return response

    def get(self, request):
        """
        获取购物车记录
        1、获取用户的购物车记录
            1.1 如果用户已登录，则从redis中获取登录用户的购物车信息  hash中的商品id和count数量 set中的商品状态 seleted
            1.2 如果用户未登录，则从cookie中获取用户的购物车信息
        2、根据购物记录中的商品id获取对应的商品信息
        3、把购物车商品的数据序列化返回
        """
        # 获取用户user  如果是无效的jwt token，此过程会出发DRF框架认证机制 主动异常捕获
        try:
            user = request.user
        except Exception:
            user = None

        # 1、获取登录用户的购物记录  使用and 保证检测完整性 匿名用户存在也不能进入条件if内部
        if user and user.is_authenticated:
            # 1.1 用户已登录,在redis中保存用户的购物记录
            # 获取redis连接
            redis_conn = get_redis_connection('cart')

            # 从redis hash中取出已登录用户的购物车的信息（商品id和数量count）
            # 获取hash存储的键 登录用户的id
            cart_key = 'cart_%s' % user.id
            # {
            #     b'<sku_id>':'<count>'
            #     .......
            # }
            # 从redis hash中取出对应的购物车信息 字典类型
            cart_dict = redis_conn.hgetall(cart_key)

            # 获取set中存储的键
            cart_selected_key = 'cart_selected_%s' % user.id
            # 根据键获取值
            cart_seleted_redis = redis_conn.smembers(cart_selected_key)

            # 组织数据
            # {
            #     '<sku_id>': {
            #         'count': '<count>',
            #         'seleted':'<seleted>'
            #     }
            # }
            cart = {}
            # selected 这个参数不是必须的
            for sku_id, count in cart_dict.items():
                cart[int(sku_id)] = {
                    'count': int(count),
                    'selected': sku_id in cart_seleted_redis
                }
        else:
            # 1.2 未登录用户 从cookie中取出购物车记录信息
            cookie_cart = request.COOKIES.get('cart')

            if cookie_cart:
                cart = pickle.loads(base64.b64decode(cookie_cart.encode()))
            else:
                cart = {}

        # 2、根据购物车记录中的商品id值，获取对应商品的信息
        # 现在cart中存储的用户的购物商品的id
        sku_ids = cart.keys()

        # 查询数据库
        skus = SKU.objects.filter(id__in=sku_ids)

        # 进行遍历
        for sku in skus:
            # 给sku对象增添count和selected属性
            # 分别保存该商品在购物车中添加的数量和勾选状态
            sku.count = cart[sku.id]['count']
            sku.selected = cart[sku.id]['selected']

        # 3、将购物车记录序列化返回
        serializer = CartSKUSerializer(skus, many=True)
        return Response(serializer.data)

    def post(self, request):
        """
        request.user:
            如果用户已认证， request.user就是登录用户类对象
            如果用户未认证，request.user 就是匿名用户类对象
        购物车记增加
        1、获取参数并进行校验（参数的完整性、sku_id对应商品是否存在，存在的话，count值是否大于库存量）
        2、保存用户购物记录
            1、用户已登录，在redis中保存用户的购物记录,hash 保存  set保存
            2、用户未登录，在cookie中保存用户的购物信息
        3、返回应答，添加购物记录成功
        """

        # 1、获取参数并进行校验（参数的完整性、sku_id对应商品是否存在，存在的话，count值是否大于库存量）
        serializer = CartSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        # 获取校验后数据
        sku_id = serializer.validated_data['sku_id']
        count = serializer.validated_data['count']
        selected = serializer.validated_data['selected']

        # 获取用户user  如果是无效的jwt token，此过程会出发DRF框架认证机制 主动异常捕获
        try:
            user = request.user
        except Exception:
            user = None

        # 2、保存用户的购物记录  使用and 保证检测完整性 匿名用户存在也不能进入条件if内部
        if user and user.is_authenticated:
            # 2.1如果用户已登录,在redis中保存用户的购物记录
            # 获取redis连接
            redis_conn = get_redis_connection('cart')

            # 获取hash存储的键 登录用户的id
            cart_key = 'cart_%s' % user.id
            # 在redis中存储登录用户添加商品的id和数量count
            # 如果用户的购物车已经添加过该商品，数量count进行累加即可
            # hincrby 功能：如果购物车已经添加过，则在原有的基础上累加，如果没有，则添加新的
            # hash : 存储登录用户的购物车中添加的商品id和对应的count
            redis_conn.hincrby(cart_key, sku_id, count)

            # set:存储的是登录用户的购物车中被勾选的商品id
            # 获取set存储的键 登录用户的id
            cart_selected_key = 'cart_selected_%s' % user.id
            # 校验selected是否存在
            if selected:
                redis_conn.sadd(cart_selected_key, sku_id)

            return Response(serializer.validated_data, status=status.HTTP_201_CREATED)

        else:
            # 2.2、表示未登录用户访问  采用的是cookie保存用户的购物车信息
            # 获取原始cookie中的数据
            cookie_cart = request.COOKIES.get('cart')  # 也可能为NONE

            if cookie_cart:
                # 说明购物车中有记录 解析cookie中的购物车数据
                # {
                #     '<sku_id>': {
                #         'count': '<count>',
                #         'selected': '<selected>'
                #     },
                #     ......
                # }
                # 将cookie中的字符串转换为字典
                cart_dict = pickle.loads(base64.b64decode(cookie_cart.encode()))
            else:
                cart_dict = {}

            # 在cookie购物车中添加用户的商品id和数量count
            # 首先判断用户添加的商品在不在cookie的购物车中
            if sku_id in cart_dict:
                count += cart_dict[sku_id]['count']

            # 保存哪本对应内容
            cart_dict[sku_id] = {
                'count': count,
                'selected': selected
            }

            # 返回应答
            response = Response(serializer.validated_data, status=status.HTTP_201_CREATED)
            # 设置cookie的购物车数据，我们处理过得是字典类型，cookie中保存的都是字符串类型，要进行一个类型转换
            cart_data = base64.b64encode(pickle.dumps(cart_dict)).decode()
            # 设置存储的cookie值
            response.set_cookie('cart', cart_data, max_age=constants.CART_COOKIE_EXPIRES)

            return response

# /cart/selection/
class CartSelectView(APIView):
    def perform_authentication(self, request):
        """跳过DRF框架认证过程"""
        pass

    def put(self, request):
        """
        购物车记录全选和取消全选
        1、获取参数selected并进行校验
        2、修改购物车记录勾选状态
            2.1 如果用户已登录，操作redis中的购物车记录
            2.2 如果用户未登录，操作cookie中的购物车记录
        3、返回应答，操作成功
        """

        # 1、获取参数selected并进行校验
        serializer = CartSelectSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)

        # 获取校验后的selected
        selected = serializer.validated_data['selected']  # True 代表全部选中  False 全部不选中

        try:
            user = request.user
        except Exception:
            user = None

        # 2、修改购物车勾选状态
        if user and user.is_authenticated:
            # 2.1 用户已登录 操作redis数据库中对应的购物车记录
            # 建立连接
            redis_conn = get_redis_connection('cart')

            # 取键 然后从redis hash中获取购物车中所有的商品id
            cart_key = 'cart_%s' % user.id
            # [b'<sku_id>',b'<sku_id>'.....]
            sku_ids = redis_conn.hkeys(cart_key)

            # 取键 从redis的set中获取购物车商品的勾选状态
            cart_selected_key = 'cart_selected_%s' % user.id

            if selected:
                # 说明全部选中 用户购物车中所有的商品的sku_id全部添加到redis的set中
                redis_conn.sadd(cart_selected_key, *sku_ids)
            else:
                # 说明全部取消 用户所有的购物车中的商品的sku_id全部从redis的set中取消
                redis_conn.srem(cart_selected_key, *sku_ids)

            # 返回应答
            return Response({'message': 'OK'})

        else:
            # 2.2 用户未登录 操作cookie中的购物车记录
            # 获取cookie中的购物车记录
            cookie_cart = request.COOKIES.get('cart')

            if cookie_cart:
                # 购物车有记录，解析
                # {
                #     '<sku_id>': {
                #         'count': '<count>',
                #         'selected': '<selected>'
                #     },
                #     ....
                # }
                cart_dict = pickle.loads(base64.b64decode(cookie_cart.encode()))

            else:
                cart_dict = {}

            # 设置购物车勾选状态
            for sku_id in cart_dict.keys():
                cart_dict[sku_id]['selected'] = selected

            # 3、返回应答 操作成功
            response = Response({'message': 'OK'})
            # 设置cookie中数据
            cart_data = base64.b64encode(pickle.dumps(cart_dict)).decode()
            response.set_cookie('cart', cart_data, max_age=constants.CART_COOKIE_EXPIRES)
            return response

